﻿
using Swifter.RW;
using Swifter.Tools;

using System;
using System.Collections.Generic;
using System.Data.Common;
#if NET20 || NET30 || NET35

namespace Swifter.Data
{
    internal sealed class DbRowObject: IDataReader<string>
    {
#else
using System.Dynamic;

namespace Swifter.Data
{
    internal sealed class DbRowObject : DynamicObject, IDataReader<string>
    {
        void AssertIgnoreCase(bool value)
        {
            if (value)
            {
                Throw();
            }

            void Throw()
            {
                throw new NotSupportedException("IgnoreCase");
            }
        }

        void AssertOneArguments(int length)
        {
            if (length != 1)
            {
                Throw();
            }

            void Throw()
            {
                throw new NotSupportedException("One Arguments");
            }
        }

        public override bool TryGetMember(GetMemberBinder binder, out object result)
        {
            AssertIgnoreCase(binder.IgnoreCase);

            if (map.TryGetValue(binder.Name, out var index))
            {
                result = values[index];

                return true;
            }

            result = null;

            return false;
        }

        public override bool TrySetMember(SetMemberBinder binder, object value)
        {
            AssertIgnoreCase(binder.IgnoreCase);

            if (map.TryGetValue(binder.Name, out var index))
            {
                values[index] = value;

                return true;
            }

            return false;
        }

        private bool GetIndexMap(object index, out int mapIndex)
        {
            mapIndex = -1;

            if (index == null)
            {
                return false;
            }

            if (index is int int32Index)
            {
                mapIndex = int32Index;

                return true;
            }

            if (index is string stringIndex && map.TryGetValue(stringIndex, out mapIndex))
            {
                return true;
            }

            return false;
        }

        public override bool TryGetIndex(GetIndexBinder binder, object[] indexes, out object result)
        {
            AssertOneArguments(indexes.Length);
            
            if (GetIndexMap(indexes[0], out var index))
            {
                result = values[index];

                return true;
            }

            result = null;

            return false;
        }

        public override bool TrySetIndex(SetIndexBinder binder, object[] indexes, object value)
        {
            AssertOneArguments(indexes.Length);

            if (GetIndexMap(indexes[0], out var index))
            {
                values[index] = value;

                return true;
            }

            return false;

        }

#endif

        static DbRowObject()
        {
            ValueInterface<DbRowObject>.SetInterface(new DbRowObjectInterface());
        }

        readonly DbRowObjectMap map;
        readonly object[] values;

        internal DbRowObject(DbRowObjectMap map, DbDataReader dbDataReader)
        {
            this.map = map;

            values = new object[map.Count];

            for (int i = 0; i < values.Length; i++)
            {
                if ((values[i] = dbDataReader[i]) == DBNull.Value) values[i] = null;
            }
        }

        public IEnumerable<string> Keys => map.Keys;

        public int Count => map.Count;

        object IDataReader.ReferenceToken => null;

        public IValueReader this[string key]
        {
            get
            {
                if (map.TryGetValue(key, out var index))
                {
                    var valueCopyer = new ValueCopyer();

                    valueCopyer.DirectWrite(values[index]);

                    return valueCopyer;
                }

                return null;
            }
        }
        
        public void OnReadValue(string key, IValueWriter valueWriter)
        {
            valueWriter.DirectWrite(values[map[key]]);
        }

        public void OnReadAll(IDataWriter<string> dataWriter)
        {
            var keys = map.Keys;

            for (int i = 0; i < keys.Length; i++)
            {
                dataWriter[keys[i]].DirectWrite(values[i]);
            }
        }

        public void OnReadAll(IDataWriter<string> dataWriter, IValueFilter<string> valueFilter)
        {
            var writer = new DataFilterWriter<string>(dataWriter, valueFilter);

            var keys = map.Keys;

            for (int i = 0; i < keys.Length; i++)
            {
                writer[keys[i]].DirectWrite(values[i]);
            }
        }
    }

    internal sealed class DbRowObjectMap : BaseCache<string, int>
    {
        public readonly string[] Keys;

        public DbRowObjectMap(DbDataReader dbDataReader)
            : base(dbDataReader.FieldCount)
        {
            Keys = new string[dbDataReader.FieldCount];

            for (int i = 0; i < Keys.Length; i++)
            {
                Add(Keys[i] = dbDataReader.GetName(i), i);
            }
        }

        protected override int ComputeHashCode(string key)
        {
            return key.GetHashCode();
        }

        protected override bool Equals(string key1, string key2)
        {
            return key1 == key2;
        }
    }

    internal sealed class DbRowObjectInterface : IValueInterface<DbRowObject>
    {
        public DbRowObject ReadValue(IValueReader valueReader)
        {
            if (valueReader is IValueReader<DbRowObject> reader)
            {
                return reader.ReadValue();
            }

            throw new NotSupportedException();
        }

        public void WriteValue(IValueWriter valueWriter, DbRowObject value)
        {
            valueWriter.WriteObject(value);
        }
    }
}